package com.example.smartf.mqtt;

import com.example.smartf.WebSocket.WebSocketService;
import com.example.smartf.model.mongo.User;
import com.example.smartf.model.mysql.Deviceinfo;
import com.example.smartf.repository.mongoDB.MongoGroupUtil;
import com.example.smartf.repository.mongoDB.MongoUserUtil;
import com.example.smartf.repository.mysql.DeviceinfoRepository;
import com.example.smartf.tool.RedisUtils;
import com.example.smartf.tool.influxdb.InfluxdbDao;
import net.sf.json.JSONException;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

//始终开启订阅 上行 传感器数据
public class UpCallBack implements MqttCallback {
    //日志记录器
    private Logger logger = LoggerFactory.getLogger(getClass());

    private Map<String,Integer> countmap = new Hashtable<>();


    private void sendMail(String title,String content,String email){
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(SaveData.saveData.from);
        message.setSubject(title);
        message.setTo(email);
        message.setText(content);
        SaveData.saveData.mailSender.send(message);
    }

    //连接断开时， 需要断线重连
    @Override
    public void connectionLost(Throwable throwable){
        logger.warn("mqtt 代理 : 失去连接 ，可以重新连接 ！");
        while (true){
            try{
                Thread.sleep(4000);
                logger.warn("mqtt 代理 : 失去连接 ，正在重新连接 ...");
                UpMqtt.getInstance().connect();
            }catch (MqttException e){
                logger.warn("mqtt 代理 : 连接失败 ， 错误码 : " + e.getReasonCode());
                continue;
            }catch (InterruptedException e){
                continue;
            }
            return ;
        }
    }
    //发送信息成功是回调
    @Override
    public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken){
        logger.info("message deliveryComplete ------- " + iMqttDeliveryToken.isComplete());
    }

    //接受信息成功时 的回调
    //该方法为单线程操作， 数据处理（处理，存库influxdb） 最好放到线程中
    @Override
    public void messageArrived(String topic , MqttMessage mqttMessage) throws Exception{
        logger.info("上线 -- 收到环境数据");
        if(countmap.containsKey(topic) == false){
            countmap.put(topic,0);
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                //string 转换成 json
                String jsonstr = new String(mqttMessage.getPayload());
                //System.out.println(jsonstr);
                JSONObject jsonObject = JSONObject.fromObject(jsonstr);

                //type 0 为实时数据，不存库
                int type = jsonObject.getInt("type");

                String CO2 = null;
                String EC = null;
                String LED = null;
                String LIGHT = null;
                String PH = null;
                String RH = null;
                String TEMP = null;
                try {
                    // json取出数据
                    CO2 = jsonObject.getString("co2");
                }catch (JSONException e){
                    e.printStackTrace();
                }

                try {
                    // json取出数据
                    EC = jsonObject.getString("ec");
                }catch (JSONException e){
                    e.printStackTrace();
                }
                try {
                    // json取出数据
                    LED = jsonObject.getString("LED");
                }catch (JSONException e){
                    e.printStackTrace();
                }
                try {
                    // json取出数据
                    LIGHT = jsonObject.getString("light_intensity");
                }catch (JSONException e){
                    e.printStackTrace();
                }
                try {
                    // json取出数据
                    PH = jsonObject.getString("ph");
                }catch (JSONException e){
                    e.printStackTrace();
                }
                try {
                    // json取出数据
                    RH = jsonObject.getString("humidity");
                }catch (JSONException e){
                    e.printStackTrace();
                }
                try {
                    // json取出数据
                    TEMP = jsonObject.getString("watertem");
                }catch (JSONException e){
                    e.printStackTrace();
                }

                //实时数据
                if(type == 0){
                    //向webSocket发送实时
                    if(SaveData.saveData.webSocketService.getOnlineCount() > 0) {
                        SaveData.saveData.webSocketService.sendInfo(jsonstr, topic);
                    }

                    //将最新一条实时数据存入redis
                    SaveData.saveData.redisUtils.set(topic +"_latest",jsonstr);
                    SaveData.saveData.redisUtils.expire(topic+"_latest",5);

                    //判断阈值是否超出
                    Double co2 = CO2 == null ? null : Double.parseDouble(CO2);
                    Double ec = EC == null ? null : Double.parseDouble(EC);
                    Double light = LIGHT == null ? null :Double.parseDouble(LIGHT);
                    Double ph = PH == null ? null : Double.parseDouble(PH);
                    Double rh = RH == null ? null : Double.parseDouble(RH);
                    Double temp = TEMP == null ? null : Double.parseDouble(TEMP);

                    Deviceinfo deviceinfo = SaveData.saveData.deviceinfoRepository.findByDeviceid(topic);

                    StringBuilder mailcontext = new StringBuilder();
                    //构造报警语句
                    if(co2 != null) {
                        if (deviceinfo.getCo2max() != null && co2 > deviceinfo.getCo2max() || deviceinfo.getCo2min() != null && co2 < deviceinfo.getCo2min()) {
                            mailcontext.append("CO2 超出阈值 ，当前 CO2：" + CO2 + "\n");
                        }
                    }
                    if( ec != null) {
                        if (deviceinfo.getEcmax() != null && ec > deviceinfo.getEcmax() || deviceinfo.getEcmin() != null && ec < deviceinfo.getEcmin()) {
                            mailcontext.append("EC 超出阈值 ，当前 EC：" + EC + "\n");
                        }
                    }
                    if( light != null) {
                        if (deviceinfo.getLightmax() != null && light > deviceinfo.getLightmax() || deviceinfo.getLightmin() != null && light < deviceinfo.getLightmin()) {
                            mailcontext.append("Light 超出阈值 ，当前 Light：" + LIGHT + "\n");
                        }
                    }
                    if( ph != null) {
                        if (deviceinfo.getPhmax() != null && ph > deviceinfo.getPhmax() || deviceinfo.getPhmin() != null && ph < deviceinfo.getPhmin()) {
                            mailcontext.append("PH 超出阈值 ，当前PH：" + PH + "\n");
                        }
                    }
                    if( rh != null) {
                        if (deviceinfo.getRhmax() != null && rh > deviceinfo.getRhmax() || deviceinfo.getRhmin() != null && rh < deviceinfo.getRhmin()) {
                            mailcontext.append("RH 超出阈值 ，当前RH：" + RH + "\n");
                        }
                    }
                    if( temp != null) {
                        if (deviceinfo.getTempmax() != null && temp > deviceinfo.getTempmax() || deviceinfo.getTempmin() != null && temp < deviceinfo.getTempmin()) {
                            mailcontext.append("Temp 超出阈值 ，当前Temp：" + TEMP + "\n");
                        }
                    }
                    //超出阈值的实时数据，小于3条 不发送
                    if( mailcontext.length() != 0 && countmap.get(topic) <= 3){
                        countmap.put(topic,countmap.get(topic)+1);
                        return;
                    }
                    //遍历用户 发送报警消息
                    if( mailcontext.length() != 0 ) {
                        String[] usersmail = SaveData.saveData.mongoGroupUtil.findGroupBydeviceID(topic).getUsers();

                        for (String usermail : usersmail) {
                            if( SaveData.saveData.redisUtils.get(usermail) == null ) {
                                //用户名redis存库操作必须在发邮件操作之前
                                //sendMail操作为阻塞操作，通常发送在 if语句执行之后10秒才响应
                                //而实时数据此时可能已经过来了好几条，而且此时存redis语句在senMail之后的话，
                                //                             redis还没有存库，也就是说这几条实时通过了if语句并也进行了邮箱发送
                                //从而造成了短时间内重复发送的问题

                                SaveData.saveData.redisUtils.set(usermail, "mail sent");
                                SaveData.saveData.redisUtils.expire(usermail, 240);
                                //向邮箱发送报警邮件
                                new Thread(new Runnable() {
                                    @Override
                                    public void run() {
                                        sendMail("智慧种植系统", mailcontext.toString(), usermail);
                                        logger.info("阈值报警 ：" + usermail + " 发送成功！");
                                    }
                                }).start();
                            }
                        }
                        //报警邮件发送完成后 计数重置
                        countmap.put(topic,0);
                    }
                    // 实时数据不存库 ， 直接返回
                    return;
                }
                //取出传感器状态信息
                int devRelay16 = jsonObject.getInt("devRelay16");
                int devEnv = jsonObject.getInt("devEnv");
                int devLight = jsonObject.getInt("devLight");
                int devEC = jsonObject.getInt("devEC");
                int devPH = jsonObject.getInt("devPH");

                //设备状态为0 则为异常数据   直接抛弃
                if( devRelay16 == 0 || devEnv == 0 || devLight == 0 || devEC == 0 || devPH == 0){
                    return;
                }



                //计算天数之差
                DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
                Date startdate = null;
                try {
                    startdate = format.parse(SaveData.saveData.deviceinfoRepository.findByDeviceid(topic).getStartday());
                }catch (ParseException e){
                    logger.warn(topic + "天数差计算错误："+ e.getMessage());
                }
                Integer days =  (int)(  (new Date().getTime() - startdate.getTime())  /(24*3600*1000)) ;


                //构造tags fields map
                Map<String,String> tags = new HashMap<>();
                tags.put("days",days.toString());
                Map<String,Object> fields = new HashMap<>();
                fields.put("CO2",CO2);
                fields.put("EC",EC);
                fields.put("LED",LED);
                fields.put("LIGHT",LIGHT);
                fields.put("PH",PH);
                fields.put("RH",RH);
                fields.put("TEMP",TEMP);

                System.out.println(tags);
                System.out.println(fields);
                //放入influxDB数据库
                SaveData.saveData.influxdbDao.insert(topic,tags,fields);
            }
        }).start();
    }

    // 此类 用来保存实时数据到数据库
    @Component
    public static class SaveData{
        @Resource
        private InfluxdbDao influxdbDao;
        @Resource
        private WebSocketService webSocketService;
        @Resource
        private MongoGroupUtil mongoGroupUtil;
        @Autowired
        private DeviceinfoRepository deviceinfoRepository;
        @Autowired
        private RedisUtils redisUtils;
        @Autowired
        private JavaMailSender mailSender;

        @Value("1033457564@qq.com")
        private String from;

        private static SaveData saveData;

        @PostConstruct
        public void init(){
            saveData = this;
            saveData.influxdbDao = this.influxdbDao;
            saveData.webSocketService = this.webSocketService;
            saveData.mongoGroupUtil = this.mongoGroupUtil;
            saveData.deviceinfoRepository = this.deviceinfoRepository;
            saveData.redisUtils = this.redisUtils;
            saveData.mailSender = this.mailSender;
            saveData.from = this.from;
        }
    }
}
